﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using Newtonsoft.Json.Serialization;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer.ConnectionStrings;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESG.Entities;
using Sustainalytics.ESGRatings.BusinessLogic.ESGOnlineReport;
using Sustainalytics.ESGRatings.BusinessLogic.Exceptions;
using Sustainalytics.ESGRatings.DataContract.ESG;
using Sustainalytics.ReportGenerator.BL;
using Sustainalytics.Utils;
using Sustainalytics.Utils.Enumerable;

namespace Sustainalytics.ESGRatings.BusinessLogic
{
    public class PdfReportExecutor : IPdfReportExecutor
    {
        private readonly string _clientDwConnectionString;
        private readonly string _clientPlatformConnectionString;
        private readonly string _dwArchiveConnectionString;
        private readonly string _esgComputationServiceUrl;

        private readonly int _paralelismDegree;



        public PdfReportExecutor()
        {
            _clientPlatformConnectionString = ConfigurationManager.ConnectionStrings["ClientPlatform"].ConnectionString;
            _clientDwConnectionString = GetConnectionString(ConnectionStringType.ClientDW);
            _dwArchiveConnectionString = GetConnectionString(ConnectionStringType.DWArchive);

            _esgComputationServiceUrl = ConfigurationManager.AppSettings["ESGComputingServiceUrl"];


            int value = 1;
            var cfgValue = (string)ConfigurationManager.AppSettings["MaxDegreeOfParallelism"];
            if (!string.IsNullOrEmpty(cfgValue))
                int.TryParse(cfgValue, out value);
            _paralelismDegree = Math.Max(value, 1);
        }

        public PdfReportExecutor(string clientDwConnectionString, string clientPlatformConnectionString,
            string dwArchiveConnectionString, string computationUrl)
        {
            _clientDwConnectionString = clientDwConnectionString;
            _clientPlatformConnectionString = clientPlatformConnectionString;
            _dwArchiveConnectionString = dwArchiveConnectionString;

            _esgComputationServiceUrl = computationUrl;
            _paralelismDegree = 1;
        }

        public PdfReportExecutionResult ExecuteEsgCompanyReport(EsgCompanyReportParameters parameters)
        {
            var log = new List<string>();
            var sw = Stopwatch.StartNew();


            var crdo = GetCompanyReportDataInParallel(parameters);

            sw.Stop();

            log.Add(crdo.CorporateData.CompanyName);
            log.Add(crdo.CompanyId.ToString());
            log.Add(string.Format("{0} ms", sw.ElapsedMilliseconds));

            sw.Restart();

            var reportGenerator = new ReportGenerator.BL.ReportGenerator();

            var result = reportGenerator.GenerateReportPdf(crdo, _paralelismDegree > 1);

            var executionResult = new PdfReportExecutionResult
            {
                Id = Guid.NewGuid(),
                CompanyId = result[0].CompanyId,
                EsgProfileId = null,
                FileName = result[0].FileName,
                FileContent = result[0].FileContent
            };

            sw.Stop();

            log.Add(string.Format("{0} ms", sw.ElapsedMilliseconds));

            executionResult.ExecutionLog = log.ToArray();

            return executionResult;
        }

        private static ESGRatingsOptionsDto GetEsgRatingsOptions(Entities.ESGRatings.ESGProfile esgProfile, IEnumerable<Guid> companyIds)
        {
            var options = new ESGRatingsOptionsDto
            {
                MatrixId = esgProfile.MatrixId,
                RefUnivId = esgProfile.ReferenceUniverseId,
                CompaniesIds = new List<Guid>(companyIds),
                GroupBy = ComparativeGroupEnum.PeerGroup,
                CompanyType = CompanyTypeEnum.Combined,
                TemplateType = TemplateTypeEnum.Combined,
                IncludeAggregatedScores = true,
                IncludeRatings = true,
                IncludeWeights = true,
                IncludeTopPerformersInfo = false,
                IncludeIndWeightedScores = true,
                IncludeAggregatedWeights = true,
                IncludeCompaniesInGroup = true,
            };

            options.GroupBy = esgProfile.ComparativeGroup;
            options.CompanyType = esgProfile.CompanyType;
            options.TemplateType = esgProfile.TemplateType;

            return options;
        }

        private Entities.ESGRatings.ESGProfile GetEsgCustomProfileFromParameters(EsgCompanyReportParameters parameters, CorporateData corporateData)
        {
            var esgProfile = new Entities.ESGRatings.ESGProfile
            {
                Id = parameters.EsgProfileId != Guid.Empty ? parameters.EsgProfileId : Guid.NewGuid(),
                MatrixId = parameters.MatrixId,
                ReferenceUniverseName = parameters.ReferenceUniverseName,
                MatrixName = parameters.MatrixName,
                ReferenceUniverseId = parameters.ReferenceUniverseId,
                CompanyType = CompanyTypeEnum.Combined,
                TemplateType = TemplateTypeEnum.Combined,
                ComparativeGroup = (ComparativeGroupEnum)parameters.ComparativeGroup
            };

            if (parameters.CompanyType != (int)CompanyTypeEnum.Combined)
            {
                if (corporateData.CompanyType.Contains("Public"))
                {
                    esgProfile.CompanyType = CompanyTypeEnum.Public;
                }
                if (corporateData.CompanyType.Contains("Private"))
                {
                    esgProfile.CompanyType = CompanyTypeEnum.Private;
                }
            }

            if (parameters.TemplateType != (int)TemplateTypeEnum.Combined)
            {
                if (corporateData.TemplateType.Contains("Type A"))
                {
                    esgProfile.TemplateType = TemplateTypeEnum.TypeA;
                }
                if (corporateData.TemplateType.Contains("Type B"))
                {
                    esgProfile.TemplateType = TemplateTypeEnum.TypeB;
                }
            }

            return esgProfile;
        }

        private CompanyReportDataOffline GetCompanyReportDataInParallel(EsgCompanyReportParameters parameters)
        {
            WeightMatrix weightMatrix = null;
            ReferenceUniverse referenceUniverse = null;

            CorporateData corporateData = null;
            FinancialData financialData = null;
            var financialDataDictionary = new Dictionary<Guid, FinancialData>(0);
            RPCompanyProfile rpCompanyProfile = new RPCompanyProfile();

            OverallPerformance overallPerformance = null;
            List<CompanyScore> industryTopPerformers = null;
            List<CompanyScore> industryPeers = null;
            Tuple<double, double> marketInfo = null;
            Dictionary<string, ESGRatingsAdapter.IndicatorDataStruct> indicatorsData = null;

            List<CompanyHistoryData> companyHistoricalPerformance = null;

            Analyst analyst = null;

            var subPeerGroupKeyEsgIssuesDictionary = new Dictionary<int, List<SubPeerGroupKeyEsgIssue>>();

            List<SubPeerGroupKeyEsgIssue> subPeerGroupKeyEsgIssues = null;
            List<KeyEsgIssue> keyEsgIssues = null;

            IList<IndicatorTemplate> indicatorTemplates = null;
            List<ClientDW.Entities.ThemePerformance> themesPerformance = null;
            List<RPIndicatorExtended> standardIndicators = null;
            List<RPControversyIndicator> controversyIndicators = null;

            List<EventIndicator> eventIndicators = null;
            List<Incident> incidents = null;
            List<Source> sources = null;

            var esgThresholds = new EsgThresholdManager<EsgFocusThresholds, string>(_clientDwConnectionString);

            new Action[]
            {
                () =>
                {
                    var corporateDataRepo = new RepositoryCollection<CorporateData>(_clientDwConnectionString);
                    corporateData = corporateDataRepo.Read(parameters.CompanyId);
                },
                () =>
                {
                    var rpCompanyProfileRepo = new RepositoryCollection<RPCompanyProfile>(_clientDwConnectionString);
                    rpCompanyProfile = rpCompanyProfileRepo.Read(parameters.CompanyId);
                },
                () =>
                {
                    weightMatrix = (new RepositoryCollection<WeightMatrix>(_clientDwConnectionString))
                        .ReadAllWhere(x => x.Id == parameters.MatrixId)
                        .FirstOrDefault();
                },
                () =>
                {
                    referenceUniverse = (new RepositoryCollection<ReferenceUniverse>(_clientDwConnectionString))
                        .ReadAllWhere(x => x.Id == parameters.ReferenceUniverseId)
                        .FirstOrDefault();
                },
                () =>
                {
                    var subPeerGroupKeyEsgIssueRepo = new RepositoryCollection<SubPeerGroupKeyEsgIssue>(_clientDwConnectionString);
                    var allSubPeerGroupKeyEsgIssues = subPeerGroupKeyEsgIssueRepo.ReadAll();


                    var subPeerGroupKeis = allSubPeerGroupKeyEsgIssues.GroupBy(x => x.SubPeerGroupId).ToList();
                    subPeerGroupKeis.ForEach(x => subPeerGroupKeyEsgIssuesDictionary.Add(x.Key, x.ToList()));
                }

            }.RunInParallel(_paralelismDegree);

            parameters.MatrixName = weightMatrix != null ? weightMatrix.Name : string.Empty;
            parameters.ReferenceUniverseName = referenceUniverse != null ? referenceUniverse.Name : string.Empty;

            var esgProfile = GetEsgCustomProfileFromParameters(parameters, corporateData);


            new Action[]
            {
                () =>
                {
                    if (parameters.HasComprehensiveResearch)
                    {
                        var esgRatingsAdapter = GetEsgRatingsAdapter(esgProfile, new[] {parameters.CompanyId});

                        var esgthresholds = esgThresholds.GetCompanyEsgThresholds();
                        esgRatingsAdapter.LoadEsgFocus(esgthresholds);

                        overallPerformance = esgRatingsAdapter.GetCompanyPerformance(parameters.CompanyId);
                        themesPerformance = esgRatingsAdapter.GetThemesPerformance(parameters.CompanyId);


                        var neededInfoCompanyId = esgRatingsAdapter.GetCompaniesInGroup(parameters.CompanyId);
                        financialDataDictionary = GetFinancialData(neededInfoCompanyId.ToList());
                        esgRatingsAdapter.LoadFinancialData(financialDataDictionary);

                        financialData = financialDataDictionary.ContainsKey(parameters.CompanyId)
                            ? financialDataDictionary[parameters.CompanyId]
                            : null;

                        industryPeers = esgRatingsAdapter.GetIndustryPeers(parameters.CompanyId);
                        industryTopPerformers = esgRatingsAdapter.ComparativeGroupTop5ByCompanyId(parameters.CompanyId);
                        marketInfo = esgRatingsAdapter.ComputeMarketCapitalization(industryPeers);
                        indicatorsData = esgRatingsAdapter.GetCompanyIndicatorData(parameters.CompanyId);
                    }
                    else
                    {
                        var financialDataRepo = new RepositoryCollection<FinancialData>(_clientDwConnectionString);
                        financialData = financialDataRepo.Read(parameters.CompanyId);

                        var computationResultRepo = new RepositoryCollection<RPCompanyESGComputationResult>(_clientDwConnectionString);

                        var esgComputationResults = computationResultRepo.ReadWhere(x => x.Id == parameters.CompanyId,
                            null, null, null, x => x.Id, x => x.OverallPerformance, x => x.RelativePosition, x => x.Indicators);

                        var esgComputationResult = esgComputationResults.FirstOrDefault();
                        if (esgComputationResult != null)
                        {
                            overallPerformance = new OverallPerformance()
                            {
                                CompanyScore = (int)Math.Round(esgComputationResult.OverallPerformance.Score,
                                    MidpointRounding.AwayFromZero),
                                CompanyAssessment = new RPAssessment()
                                {
                                    Id = esgComputationResult.OverallPerformance.Assessment.Id,
                                    Name = esgComputationResult.OverallPerformance.Assessment.Name
                                },
                                Percentile   = esgComputationResult.OverallPerformance.Percentile
                            };

                            industryTopPerformers = esgComputationResult.RelativePosition.TopList.Select(x => new CompanyScore()
                            {
                                CompanyId = x.CompanyId,
                                Score = x.Score,
                                Rank = x.Rank,
                                CompanyName = x.CompanyName
                            }).ToList();

                            industryPeers = esgComputationResult.RelativePosition.PeersList.Select(x => new CompanyScore()
                            {
                                CompanyId = x.CompanyId,
                                Score = x.Score,
                                Rank = x.Rank,
                                CompanyName = x.CompanyName
                            }).ToList();

                            marketInfo = new Tuple<double, double>(esgComputationResult.RelativePosition.MarketCapRangeFrom, esgComputationResult.RelativePosition.MarketCapRangeTo);
                        }
                    }
                },
                () =>
                {
                    var companyHistoricalPerformanceRepo =
                        new RepositoryCollection<CompanyHistoricalPerformance>(_dwArchiveConnectionString);

                    var historycalDataManager = new HistoricalDataBusinessLogicUnit(companyHistoricalPerformanceRepo);
                    var companyHistoricalPerformanceDic =
                        historycalDataManager.GetByIds(new List<Guid> {parameters.CompanyId});

                    companyHistoricalPerformance = companyHistoricalPerformanceDic.ContainsKey(parameters.CompanyId)
                        ? companyHistoricalPerformanceDic[parameters.CompanyId].Last36Months
                        : new List<CompanyHistoryData>();
                },
                () =>
                {
                    var indicatorTemplatesRepo = new RepositoryCollection<IndicatorTemplate>(_clientDwConnectionString);
                    indicatorTemplates = indicatorTemplatesRepo.ReadAll();
                },
                () =>
                {
                    var analystRepo = new RepositoryCollection<Analyst>(_clientDwConnectionString);
                    analyst = analystRepo.ReadWhere(x => x.Id == rpCompanyProfile.AnalystId).FirstOrDefault();
                },
                () =>
                {
                    var companyKeyEsgIssueRepo = new RepositoryCollection<CompanyKeyESGIssue>(_clientDwConnectionString);
                    var companyKeyEsgIssues = companyKeyEsgIssueRepo.ReadAllWhere(x => x.Id == parameters.CompanyId).ToList();

                    if (!parameters.HasComprehensiveResearch)
                    {
                        foreach (var companyKeyEsgIssue in companyKeyEsgIssues)
                        {
                            foreach (var kei in companyKeyEsgIssue.KeyESGIssues)
                            {
                                kei.KeyEsgIssueIndicators.Sort(KeyEsgIssueIndicatorSorter.Sort);
                            }
                        }
                    }

                    var keyEsgIssuesDictionary = companyKeyEsgIssues.ToDictionary(kei => kei.Id, kei => kei.KeyESGIssues);

                    var subPeerGroupIdDictionary = companyKeyEsgIssues.ToDictionary(kei => kei.Id, kei => kei.SubPeerGroupId);

                    var subPeerGroupId = subPeerGroupIdDictionary.ContainsKey(parameters.CompanyId)
                        ? subPeerGroupIdDictionary[parameters.CompanyId]
                        : 0;

                    subPeerGroupKeyEsgIssues = subPeerGroupKeyEsgIssuesDictionary.ContainsKey(subPeerGroupId)
                        ? subPeerGroupKeyEsgIssuesDictionary[subPeerGroupId]
                        : new List<SubPeerGroupKeyEsgIssue>();

                    keyEsgIssues = keyEsgIssuesDictionary.ContainsKey(parameters.CompanyId)
                        ? keyEsgIssuesDictionary[parameters.CompanyId].OrderBy(x=> x.SortKey?.Value).ToList()
                        : null;
                },
                () =>
                {
                    var standardIndicatorsRepo = new RepositoryCollection<RPIndicatorExtended>(_clientDwConnectionString);
                    standardIndicators = standardIndicatorsRepo.ReadAllWhere(x => x.CompanyId == parameters.CompanyId).ToList();
                },
                () =>
                {
                    var controversyIndicatorsRepo = new RepositoryCollection<RPControversyIndicator>(_clientDwConnectionString);
                    controversyIndicators = controversyIndicatorsRepo.ReadAllWhere(x => x.CompanyId == parameters.CompanyId).ToList();
                },
                () =>
                {
                    var eventIndicatorsRepo = new RepositoryCollection<EventIndicator>(_clientDwConnectionString);
                    eventIndicators = eventIndicatorsRepo.ReadAllWhere(x => x.CompanyId == parameters.CompanyId).ToList();
                },
                () =>
                {
                    var incidentsRepo = new RepositoryCollection<Incident>(_clientDwConnectionString);
                    incidents = incidentsRepo.ReadAllWhere(x => x.CompanyId == parameters.CompanyId).ToList();
                },
                () =>
                {
                    if (!parameters.HasComprehensiveResearch) return;

                    var sourcesRep = new RepositoryCollection<Source>(_clientDwConnectionString);
                    sources = sourcesRep.ReadAllWhere(s => s.CompanyId == parameters.CompanyId)
                        .OrderBy(s => s.SourceText)
                        .ToList();
                }
            }
            .RunInParallel(_paralelismDegree);


            var crdo = new CompanyReportDataOffline
            {
                CompanyId = parameters.CompanyId,

                ReportType = parameters.HasComprehensiveResearch
                    ? parameters.IsSummaryReport
                        ? ReportType.SummaryComprehensiveReport
                        : ReportType.ComprehensiveReport
                    : parameters.IsSummaryReport
                        ? ReportType.SummaryCoreReport
                        : ReportType.CoreReport,

                ESGProfile = esgProfile,

                CorporateData = corporateData,
                FinancialData = financialData,

                OverallPerformance = overallPerformance,
                CompanyProfile = rpCompanyProfile,

                IndustryTopPerformers = industryTopPerformers,
                IndustryPeers = industryPeers,
                IndustryPeersMinCapitalization = marketInfo != null ? marketInfo.Item1 : 0,
                IndustryPeersMaxCapitalization = marketInfo != null ? marketInfo.Item2 : 0,

                CompanyHistoricalPerformance = companyHistoricalPerformance,

                Analyst = analyst ?? new Analyst(),
                SubPeerGroupKeyEsgIssues = subPeerGroupKeyEsgIssues,
                KeyEsgIssues = keyEsgIssues.OrderBy(x => x.SortKey?.Value).ToList() ?? new List<KeyEsgIssue>(),
                AnalystCommentary = rpCompanyProfile.AnalystCommentary,

                IndicatorTemplates = indicatorTemplates,
                ThemesPerformance = themesPerformance,
                StandardIndicators = standardIndicators,
                ControversyIndicators = controversyIndicators,

                EventIndicators = eventIndicators,
                Incidents = incidents,

                Sources = sources
            };


            if (indicatorsData == null || !indicatorsData.Any()) return crdo;

            crdo.StandardIndicators = crdo.StandardIndicators
                .Where(x => indicatorsData.ContainsKey(x.Number.ToLowerInvariant()))
                .ToList();

            foreach (var x in crdo.StandardIndicators)
            {
                SetStandardIndicatorWeight(indicatorsData, x);
            }


            crdo.ControversyIndicators = crdo.ControversyIndicators
                .Where(x => indicatorsData.ContainsKey(x.Template.Number.ToLowerInvariant()))
                .ToList();

            foreach (var x in crdo.ControversyIndicators)
            {
                SetControversyIndicatorWeight(indicatorsData, x);
            }

            return crdo;
        }

        public  RPIndicatorPerformance GetIndicatorPerformance(Guid companyId, string code)
        {
            var computationResultRepo = new RepositoryCollection<RPCompanyESGComputationResult>(_clientDwConnectionString);

            var performanceIndicators = computationResultRepo.ReadWhere(x => x.Id ==companyId).Select(x=>x.Indicators).FirstOrDefault();
 
            var indicator = from perfindicator in performanceIndicators
                                where perfindicator.Code == code
                                select perfindicator;
            return indicator.FirstOrDefault();
        }

        private void SetStandardIndicatorWeight(
            IReadOnlyDictionary<string, ESGRatingsAdapter.IndicatorDataStruct> indicatorsData, RPIndicatorExtended x)
        {
            var performanceIndicator = GetIndicatorPerformance(x.CompanyId, x.Number);

            ESGRatingsAdapter.IndicatorDataStruct indicatorData;
            if (!indicatorsData.TryGetValue(x.Number.ToLowerInvariant(), out indicatorData))
            {
                return;
            }
            x.Weight = indicatorData.Weight;
            x.WeightedScore = indicatorData.WeightedScore;
            x.SelectedAnswer.Score.Value = Convert.ToInt32(performanceIndicator.RawScore.Value);
        }

        private static void SetControversyIndicatorWeight(
            IReadOnlyDictionary<string, ESGRatingsAdapter.IndicatorDataStruct> indicatorsData, RPControversyIndicator x)
        {
            
            ESGRatingsAdapter.IndicatorDataStruct indicatorData;
            if (!indicatorsData.TryGetValue(x.Template.Number.ToLowerInvariant(), out indicatorData))
            {
                return;
            }
            x.Weight = indicatorData.Weight;
            x.WeightedScore = indicatorData.WeightedScore;
        }


        private Dictionary<Guid, FinancialData> GetFinancialData(ICollection<Guid> peerGroupCompanyIds)
        {
            var result = new Dictionary<Guid, FinancialData>();


            if (peerGroupCompanyIds.IsNullOrEmpty())
            {
                return result;
            }


            var financialDataRepo = new RepositoryCollection<FinancialData>(_clientDwConnectionString);
            var financialData =
                financialDataRepo.ReadWhere(x => peerGroupCompanyIds.Contains(x.Id));
            if (financialData != null)
            {
                return financialData.ToDictionary(x => x.Id, x => x);
            }


            return result;
        }

        private string GetConnectionString(ConnectionStringType connectionStringType)
        {
            var connectionStringsManager =
                ConnectionStringsManager.GetInstance(
                    ConnectionStringsRepository.GetInstance(_clientPlatformConnectionString));

            return connectionStringsManager.GetConnectionString(connectionStringType);
        }

        private ESGRatingsAdapter GetEsgRatingsAdapter(Entities.ESGRatings.ESGProfile esgProfile, ICollection<Guid> computationCompanyIds)
        {
            if (computationCompanyIds.IsNullOrEmpty())
            {
                throw new ArgumentNullException("computationCompanyIds", "Invalid companies ids computation parameters");
            }

            var uri = new Uri(string.Format("{0}/esgcompute", _esgComputationServiceUrl));
            
            var options = GetEsgRatingsOptions(esgProfile, computationCompanyIds);

            var esgRatings = new ESGRatingsDto();

            var jsonMediaTypeFormatter = new JsonMediaTypeFormatter
            {
                SerializerSettings =
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }
            };


            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));


                var result = client.PostAsync(uri, options, jsonMediaTypeFormatter).Result;
                if (result.IsSuccessStatusCode)
                {
                    esgRatings = result.Content.ReadAsAsync<ESGRatingsDto>(jsonMediaTypeFormatter.Yield()).Result;
                }
                if (!result.IsSuccessStatusCode)
                {
                    throw new EsgComputingException(
                        string.Format("An error occured when calling the esg computing service {0} : {1}", uri,
                            result.Content.ReadAsStringAsync().Result));
                }
            }


            return new ESGRatingsAdapter(esgRatings);
        }

    }
}